/**
 * 通用表格相关
 * @author rxliuli
 */

/**
 * 表单搜索时的一些常量
 * @author rxliuli
 */
class SearchActionConstant {
    /**
     * 精确搜索
     * @type {Number}
     */
    static ACCURATE = 1
    /**
     * 模糊搜索
     * @type {Number}
     */
    static BLURRY = 2
}

Vue.component('rx-range-date', {
    model: {
        prop: 'range',
        event: 'change',
    },
    props: {
        range: Array,
    },
    computed: {
        innerRange: {
            get() {
                return this.range
            },
            set(val) {
                this.$emit('change', val)
            },
        },
    },
    template: `<el-date-picker
  v-model="innerRange"
  type="daterange"
  range-separator="至"
  start-placeholder="开始日期"
  end-placeholder="结束日期"
  :default-time="['00:00:00', '23:59:59']"
/>
    `,
})

Vue.component('rx-range-time', {
    model: {
        prop: 'range',
        event: 'change',
    },
    props: {
        range: Array,
    },
    computed: {
        innerRange: {
            get() {
                return this.range
            },
            set(val) {
                this.$emit('change', val)
            },
        },
    },
    template: `<el-time-picker
  v-model="innerRange"
  is-range
  range-separator="至"
  start-placeholder="开始时间"
  end-placeholder="结束时间"
  :default-time="['00:00:00', '23:59:59']"
/>
    `,
})

/**
 * 分页信息，多次使用到所以定义一个公共的
 */
class Page {
    /**
     * 构造函数
     * @param {Number} current 当前页数，从 1 开始
     * @param {Number} size 每页的数量
     * @param {Number} total 数据总条数
     * @param {Number} pages 数据总页数
     * @param {Array} records 一页的数据记录
     * @param {String} [orderByField] 需要进行排序的列，默认为空，按照数据库自然排序
     * @param {Boolean} [isAsc=true] 是否正向排序
     * @param {...Object} [args] 其余的属性值，最后合并到 this 上
     * @returns {Page} 分页对象
     */
    constructor({
                    current = 1,
                    size = 10,
                    total = 0,
                    pages = 0,
                    records = [],
                    orderByField = '',
                    asc = true,
                    ...args
                } = {}) {
        this.current = current
        this.size = size
        this.total = total
        this.pages = pages
        this.records = records
        this.orderByField = orderByField
        this.asc = asc
        Object.assign(this, args)
    }
}

/**
 * 表格的列
 */
class TableColumn {
    /**
     * 格式化日期事件
     * @param value 字段的值
     * @returns {String|*} 格式化得到的日期时间字符串 TableColumn.datetimeFormat()
     */
    static datetimeFormat(_row, _column, value, _index) {
        return !value ? '' : rx.dateFormat(new Date(value), 'yyyy-MM-dd hh:mm:ss')
    }

    static dateFormat(_row, _column, value, _index) {
        return !value ? '' : rx.dateFormat(new Date(value), 'yyyy-MM-dd')
    }

    /**
     * 上报状态格式化
     * 报备阶段， 0：未上报，1：已上报，2：上报异常
     */
    static reportStatusFormat(_row, _column, value, _index) {
        var result = ''
        value = parseInt(value)
        switch (value) {
            case 0:
                result = '未上报'
                break
            case 1:
                result = '上报中'
                break
            case 2:
                result = '已上报'
                break
            case 3:
                result = '上报失败'
                break

        }
        return result
    }

    static generate({
                        prop,
                        type,
                        width,
                        title,
                        sortable = 'custom',
                        sortAs = prop,
                        align = 'center',
                        formatter,
                        customComponent,
                        ...args
                    }) {
        return new TableColumn({
            prop,
            type,
            width,
            title,
            sortable,
            sortAs,
            align,
            formatter,
            customComponent,
            ...args,

        })
    }

    /**
     * 构造函数
     * @param {Object} [option] 可选项
     * @param {String} [option.prop] 字段名
     * @param {String} [option.title] 标题
     * @param {'selection'} [option.type] 列类型，可以设置为选择列
     * @param {Boolean} [option.sortable='custom'] 排序方式 默认全局排序
     * @param {Number} [option.width] 宽度
     * @param {'center'} [option.align='center'] 水平对齐方式
     * @param {Function} [option.formatter] 格式化列
     * @param {Boolean} [option.customComponent] 是否自定义组件
     * @param {...Object} [option.args] 其他的参数项，这里只是为了避免遗漏
     */
    constructor({
                    prop,
                    type,
                    width,
                    title,
                    sortable = false,
                    align = 'center',
                    formatter,
                    customComponent,
                    ...args
                } = {}) {
        this.prop = prop
        this.type = type
        this.width = width
        this.align = align
        this.title = title
        this.sortable = sortable
        this.align = align
        this.formatter = formatter
        this.customComponent = customComponent
        Object.assign(this, args)
    }
}

/**
 * 自定义表格组件
 */
Vue.component('my-table', {
    /**
     * 列
     */
    props: {
        columns: {
            type: Array,
            default: [],
        },
        data: {
            type: Array,
            default: [],
        },
    },
    template: `<el-table
  :data="data"
  ref="myTableRef"
  tooltip-effect="dark"
  style="width: 100%"
  border
  @selection-change="handleSelectionChange"
  @sort-change="handleSortChange"
>
  <template v-for="column in columns">

    <el-table-column
      :type="column.type"
      :prop="column.prop"
      :alias="column.alias"
      :label="column.title"
      :align="column.align"
      :sortable="column.sortable"
      :width="column.width"
      :formatter="column.formatter"
      v-if="column.customComponent"
    >
      <!--suppress HtmlUnknownAttribute -->
      <template #default="scope" >
        <!--这里将传递给模板当前行的数据-->
        <slot
          :name="humpToLine(column)"
          :row="scope.row"
        ></slot>
      </template>
    </el-table-column>
    <el-table-column
      :type="column.type"
      :prop="column.prop"
      :alias="column.alias"
      :label="column.title"
      :align="column.align"
      :sortable="column.sortable"
      :width="column.width"
      :formatter="column.formatter"
      v-else
    >
    </el-table-column>
  </template>

</el-table>`,
    methods: {
        handleSelectionChange(val) {
            this.$emit('handle-selection-change', val)
        },
        humpToLine(data) {
            const {alias = '', prop = ''} = data;
            var key;
            if (alias !== '') {
                key = data.alias;
            } else {
                key = data.prop;
            }
            return rx.StringStyleUtil.convert(rx.StringStyleType.Camel, rx.StringStyleType.Snake, key)
        },
        handleSortChange(val, a, b) {
            this.$emit('handle-sort-change', val)
        },
    },
    mounted: function () {
        this.$emit('table-ref', this.$refs.myTableRef);
    }
})

/**
 * 基本的表格数据配置
 */
class BasicTableData {
    /**
     * 构造函数
     * @param {Object} option 可选项
     * @param {BaseApi} option.api api 对象，必须为 BaseApi 的子类，如果不重写，则默认需要重写 {getPage|exportFile|importFile|deleteData} 四个函数
     * @param {Array<TableColumn>} [option.columns=[]] 列信息列表，子类必须覆盖
     * @param {Set.<String>} [option.excludeFieldSet=[]] 排除的字段
     * @param {Object} [option.form={}] 搜索表单，子类一般需要覆盖（不覆盖的话可能在 html 中没有提示）
     * @param {Boolean} [option.formShow=false] 是否显示搜索表单
     * @param {Page} [option.page=new Page()] 分页信息，包含数据列表
     * @param {Array} [option.selectedIdList=[]] 选择的列表 id
     * * @param {Array} [option.selectedList=[]] 选择的列表数据
     * @param {Boolean} [option.fileSelectorShow=false] 导入文件选择器是否需要
     */
    constructor({
                    api,
                    columns = [],
                    excludeFieldSet = new Set(),
                    form = {},
                    formShow = false,
                    page = new Page(),
                    selectedIdList = [],
                    selectedList = [],
                    fileSelectorShow = false,
                    validate = () => true,
                } = {}) {
        this.api = api
        this.columns = columns
        this.excludeFieldSet = excludeFieldSet
        this.form = form
        this.formShow = formShow
        this.page = page
        this.selectedIdList = selectedIdList
        this.selectedList = selectedList
        this.fileSelectorShow = fileSelectorShow
        this.validate = validate
    }
}

/**
 * 基本的表格方法
 */
class BasicTableMethods {
    /**
     * 构造函数
     * @param {Function} tableRef 把table的原生对象赋值到tableRefs上
     * @param {Function} createForm 初始化 form 表单，主要是为了自定义初始化逻辑
     * @param {Function} getPage 获取分页信息，需要覆盖
     * @param {Function} exportFile 导出文件，需要覆盖
     * @param {Function} importFileData 导入文件数据，需要覆盖
     * @param {function(Object):Promise.<void>} importFile 导入文件
     * @param {function(Object):Promise.<Boolean>} [importFileBefore] 导入文件数据之前的操作
     * @param {function(Object):Promise.<void>} importFileAfter 导入文件数据之后的操作
     * @param {Function} downloadTemplate 导入模板,导入文件必须覆盖此方法实现导入模板功能,
     * @param {Function} deleteData 删除选择的数据，需要覆盖
     * @param {Function} init 初始化函数，如果可能请使用该函数而非重写 mounted 生命周期函数，该函数会在 mounted 中调用
     * @param {Function} [resetFile] 重置导入选择的文件，必须为 input:file 绑定属性 ref="fileInput"
     * @param {Function} [searchPage] 搜索分页信息
     * @param {Function} [resetPage] 重置分页信息
     * @param {Function} [toggle] 切换搜索表单显示
     * @param {Function} [selection] 选择的 id
     * @param {Function} [sortChange] 根据选择字段排序,前提是 column.sortable='custom' ,@handle-sort-change="sortChange"
     * @param {Function} [sortFieldAdapter] 点击字段排序适配器，若要字段转换则覆盖
     * @param {Function} [changeSize] 改变一页的大小
     * @param {Function} [goto] 跳转到指定页数
     * @param {Function} [deleteSelected] 删除选择的数据项
     * @param {Function} [showFileSelector] 是否显示导入文件选择器
     * @param {function(String, String):function(Array)} [changeRangeDate] 改变日期区间，主要用于搜索表单中华含有的日期区间参数
     * @param {Function} [initCommon] 初始化功能，如果重写了 mounted 生命周期函数，请务必调用它！
     */
    constructor({
                    tableRef = function (refs) {
                        this.$refs["tableRefs"] = refs;
                    },
                    createForm = function () {
                        throw new Error('如果需要搜索条件，请重写 createForm() 方法')
                    },
                    exportFile = async function () {
                        if (!this.api.exportFile) {
                            throw new Error('如果需要导出数据，请重写 exportFile() 方法')
                        }

                        try {
                            const blob = await this.api.exportFile(this.form)
                            rx.download(blob, '导出 Excel数据.zip')
                        } catch (e) {
                            rxPrompt.dangerMsg('导出 Excel 错误！')
                        }
                    },
                    importFile = async function (input) {
                        const file = input.files[0]
                        if (!this.api.importFile) {
                            throw new Error('如果需要导出数据，请重写 importFile() 方法')
                        }
                        const blob = await this.api.importFile(file)
                        if (blob) {
                            if (blob.type.includes('x-excel')) {
                                rxPrompt.dangerMsg('导入时发现了一些错误')
                                rx.download(blob, '错误数据.xlsx')
                            } else if (blob.type.includes('json')) {
                                rxPrompt.dangerMsg('网络错误')
                            }
                            return
                        }
                        rxPrompt.successMsg('导入成功')
                        input.value = ''
                        this.searchPage()
                    },
                    importFileBefore = async function (file) {
                        if (!file) {
                            await rxPrompt.msg('请选择导入文件')
                            return false
                        }
                        if (file.name.substr(-3) !== 'csv') {
                            await rxPrompt.msg('请选择下载模板进行导入')
                            return false
                        }
                        return true
                    },
                    importFileData = async function (file) {
                        if (!this.importFile) {
                            throw new Error('如果需要导入数据，请重写 importFileData() 方法')
                        }
                    },
                    importFileAfter = async function (file) {
                        console.log('执行销毁方法')
                    },
                    deleteData = async function (idList) {
                        if (!this.api.deleteBatch) {
                            throw new Error('如果需要删除数据，请重写 deleteData 方法')
                        }
                        return this.api.deleteBatch(idList)
                    },
                    init = async function () {
                    },
                    downloadTemplate = function () {
                        if (!this.api.downloadTemplate) {
                            throw new Error('如果需要导入数据，请重写 downloadTemplate 方法，实现导入模板功能')
                        }
                        this.api.downloadTemplate('模板 Excel.xlsx')
                    },
                    resetFile = function (file) {
                        if (!file) {
                            throw new Error('如果需要清空选择文件，请为 input:file 绑定属性 ref 的值为 fileInput')
                        }
                        file.value = ''
                    },
                    searchPage = async function ({current = 1, size = 10} = {}) {
                        if (!await this.validate(this.form)) {
                            return
                        }
                        try {
                            const page = {...this.page, current, size}
                            let newPage = await this.getPage(page, this.form)
                            this.page = {...this.page, ...newPage}
                        } catch (e) {
                            console.error(e)
                            await rxPrompt.dangerMsg('查询数据失败，请刷新页面')
                        }
                    },
                    getPage = async function (page, entity) {
                        if (!this.api.page) {
                            throw new Error('如果需要自动分页，请重写 getPage() 方法')
                        }
                        return this.api.page(page, rx.excludeFields(rx.blankToNullField(entity), ...Array.from(this.excludeFieldSet)))
                    },
                    resetPage = async function () {
                        this.form = this.createForm()
                        await this.searchPage()
                    },
                    toggle = function () {
                        this.formShow = !this.formShow
                    },
                    selection = function (data) {
                        this.selectedIdList = data.map(({id}) => id)
                        this.selectedList = data
                    },
                    sortChange = async function (data) {
                        const {sortAs} = this.columns.find(({prop}) => prop === data.prop)
                        this.page.orderByField = sortAs
                        this.page.asc = data.order !== 'descending'
                        await this.searchPage(this.page)
                    },
                    changeSize = function (size) {
                        this.page.current = 1
                        this.page.size = size
                        this.searchPage(this.page)
                    },
                    goto = function (current) {
                        if (!current) {
                            current = this.page.current
                        }
                        if (current < 1) {
                            return
                        }
                        if (current > this.page.pages) {
                            return
                        }
                        this.page.previous = this.page.current
                        this.page.current = current
                        this.searchPage({current})
                    },
                    checkSelected = function (call = () => rxPrompt.warningMsg('请至少选择一项')) {
                        if (this.selectedIdList.length > 0) {
                            return true
                        }
                        call()
                        return false
                    },
                    checkSelectedReport = function () {
                        const map = rx.arrayToMap(this.page.records, ({id}) => id, ({
                                                                                        reportStatus = '',
                                                                                        isInIpSeg = ''
                                                                                    }) => reportStatus === '' ? isInIpSeg : reportStatus)
                        // 如果正在上报中则禁止
                        if (this.selectedList.every(({id}) => map.get(id) !== 1)) {
                            return true
                        }
                        rxPrompt.warningMsg('不能删除正在上报中的数据')
                        return false
                    },
                    deleteSelected = async function () {
                        if (this.checkSelected() && this.checkSelectedReport()) {
                            const res = await rxPrompt.confirm('你确定要删除吗？', ['确认', '取消'])
                            if (res) {
                                const result = await this.deleteData(this.selectedIdList)
                                if (result.code !== 200 || !result.data) {
                                    await rxPrompt.msg(`删除失败: ${result.msg}`)
                                    return
                                }
                                // noinspection JSIgnoredPromiseFromCall
                                rxPrompt.msg('删除成功')
                                this.page.current = 1
                                await this.searchPage()
                            }
                        }
                    },
                    showFileSelector = function () {
                        this.fileSelectorShow = !this.fileSelectorShow
                    },
                    changeRangeDate = function (startField, endField) {
                        return (arr) => {
                            const [start, end] = arr || []
                            this.form[startField] = start
                            this.form[endField] = end
                        }
                    },
                    initCommon = async function () {
                        if (Object.keys(this.form).length === 0) {
                            this.form = this.createForm()
                        }
                        this.searchPage()
                    },
                } = {}) {
        this.tableRef = tableRef
        this.createForm = createForm
        this.getPage = getPage
        this.searchPage = searchPage
        this.resetPage = resetPage
        this.toggle = toggle
        this.selection = selection
        this.sortChange = sortChange
        this.changeSize = changeSize
        this.downloadTemplate = downloadTemplate
        this.goto = goto
        this.exportFile = exportFile
        this.importFileBefore = importFileBefore
        this.importFileData = importFileData
        this.importFileAfter = importFileAfter
        this.importFile = importFile
        this.resetFile = resetFile
        this.deleteData = deleteData
        this.init = init
        this.checkSelected = checkSelected
        this.checkSelectedReport = checkSelectedReport
        this.deleteSelected = deleteSelected
        this.showFileSelector = showFileSelector
        this.changeRangeDate = changeRangeDate
        this.initCommon = initCommon
    }

}

/**
 * 基本的 vue 表格配置信息
 */
class BasicTableOption {
    /**
     * 构造函数
     * @param {String} [el='#app'] 标签选择器
     * @param {BasicTableData} data 数据
     * @param {BasicTableMethods} methods 方法
     * @param {Function} mounted 初始化方法
     */
    constructor({
                    el = '#app',
                    data = new BasicTableData(),
                    methods = new BasicTableMethods(),
                    mounted = async function () {
                        await this.initCommon()
                        await this.init()
                    },
                } = {}) {
        this.el = el
        this.data = data
        this.methods = methods
        this.mounted = mounted
    }
}

/**
 * 基本的表格 vue 类
 */
class BasicTableVue extends Vue {
    /**
     * 构造函数
     * @param {BasicTableOption} option 初始化选项
     * @param {BasicTableData|Function} option.data vue 的 data 数据，如果是 {@link Function} 类型，则必须返回 {@link BasicTableData} 的结构
     * @param {BasicTableMethods} option.methods vue 中的 methods 属性
     * @param {Function} option.mounted 初始化方法，如果覆盖则必须手动初始化表格
     */
    constructor({
                    data,
                    methods,
                    mounted,
                    ...args
                } = {}) {
        //注：这里为了应对 data 既有可能是对象，又有可能是函数的情况
        super(_.merge(new BasicTableOption(), {
            data: function () {
                return _.merge(
                    new BasicTableData(),
                    typeof data === 'function' ? data.call(this) : data,
                )
            },
            methods,
            mounted,
            ...args,
        }))
    }
}

Vue.component('import-excel', {
    props: {
        fileSelectorShow: {
            type: Boolean,
        },
        title: {
            type: String,
            default: '导入'
        },
    },
    template: `
<transition>
    <div class="container" v-show="fileSelectorShow">
        <div
        class="myHeader"
        
      >
        <div class="myHeader-title">{{title}}</div>
      </div>
        <el-form label-width="126px">
        <el-row :gutter="10">
            <el-col :span="24">
        <el-form-item label="请选择导入文件:">
        <a href="javascript:;" class="file">
            <input 
                id="fileInput"
                class="input_file"
                ref="fileInput"
                type="file"
                @change="changeFile"
                size="34">{{fileTitle}}
        </a>
        &nbsp;&nbsp;
        <el-tooltip class="item" effect="dark" content="导入的Excel表格必须是Microsoft Excel 1997-2007格式，数量请不要超过1000。" placement="top">
                <a href="#" @click="$emit('download-template')">下载导入模板</a>
        </el-tooltip>
        </el-form-item>
            </el-col>
        </el-row>
        </el-form>
        <div style="text-align:center; margin-bottom:5px">
          <el-button
            type="primary" 
            size="mini"
            @click="importFile"
          >确认</el-button>&nbsp;
          <el-button
            type="primary" 
            size="mini"
            @click="resetFile"
          >重置</el-button>
        </div>
    </div>
    
</transition>
    `,
    data() {
        return {
            fileTitle: "选择文件",
        }
    },
    methods: {
        importFile() {
            this.$emit('import-file', this.$refs['fileInput'])
        },
        resetFile() {
            this.$emit('reset-file', this.$refs['fileInput'])
        },
        changeFile() {
            const files = this.$refs['fileInput'].files;
            if (files.length === 0) {
                this.fileTitle = "选择文件";
                return;
            }
            const fileNames = [];
            for(const file of files){
                fileNames.push(file.name)
            }
            this.fileTitle = fileNames.join(",");
        }
    },
})
